﻿#region Header

/*
Behavioral Rating of Dancing Human Crowds based on Motion Patterns
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Drawing;
using paravili.Services;
using Sebarf.Services.Interfaces;

#endregion

namespace paravili.Steps {
	/// <summary>
	/// Description of the class
	/// </summary>
	public class AnalyseMovement : ProcessStepWithMeasurement<IList<Cluster>> {
		#region Public Properties

		[ServiceRequest]
		public IStatisticValuesLocatorService StatisticValuesLocatorService { get; set; }


		[ConfigurableNumericValue(Name = "HistoryDeep", RangeFrom = 0, RangeTo = 15)]
		public int HistoryDeep { get; set; }

		[ConfigurableNumericDoubleValue(Name = "ScaleFactor", RangeFrom = 0, RangeTo = 5, StepSize = 0.1)]
		public double ScaleFactor { get; set; }

		[ConfigurableNumericValue(Name = "HistoryDeepForRatingAveraging", RangeFrom = 1, RangeTo = 100)]
		public int HistoryDeepForRatingAveraging { get; set; }

		[ConfigurableNumericValue(Name = "HistoryDeepWeightedRating", RangeFrom = 1, RangeTo = 25)]
		public int HistoryDeepWeightedRating { get; set; }

		[ServiceRequest]
		public ICurrentImageInformationLocatorService CurrentImageInformationLocatorService { get; set; }

		[ServiceRequest]
		public IResetHandlerService ResetHandlerService {
			set { value.Reset += OnReset; }
		}

		#endregion

		#region Public Methods

		public AnalyseMovement() {
			HistoryDeep = 5;
			ScaleFactor = 1.4;
			HistoryDeepForRatingAveraging = 25;
			HistoryDeepWeightedRating = 15;

			m_ActivityHistoryQueue = new LimitedQueue(HistoryDeepWeightedRating);
			m_CoverageRatioHistoryQueue = new LimitedQueue(HistoryDeepWeightedRating);
		}

		#endregion

		#region Private Methods

		private void OnReset(object sender, EventArgs e) {
			m_activityHistoryCounter = 0;
			m_activityHistoryValue = 0;
			m_coverageHistoryValue = 0;
			m_ActivityHistoryQueue.Clear();
			m_CoverageRatioHistoryQueue.Clear();

			m_activityHistoricalMinValue = 0;
			m_activityHistoricalMaxValue = 0;
		}

		#region cardinal movement
		private bool IsMovingRight(IEnumerable<Cluster> m) {
			// http://en.wikipedia.org/wiki/Cosine#Cosine
			const double fromAngle = Math.PI / 4;
			const double toAngle = 7 * Math.PI / 4;
			int count = 0;
			foreach (Cluster previousCluster in m) {
				if (previousCluster.PreviousCluster == null) {
					continue;
				}
				if (!(previousCluster.AngleToPreviousCluster < fromAngle || previousCluster.AngleToPreviousCluster > toAngle)) {
					return false;
				}
				count++;
				if (count > HistoryDeep) {
					break;
				}
			}
			return count > 0 ? true : false;
		}

		private bool IsMovingTop(IEnumerable<Cluster> m) {
			// http://en.wikipedia.org/wiki/Cosine#Cosine
			const double fromAngle = Math.PI / 4;
			const double toAngle = 3 * Math.PI / 4;
			int count = 0;
			foreach (Cluster previousCluster in m) {
				if (previousCluster.PreviousCluster == null) {
					continue;
				}
				if (!(previousCluster.AngleToPreviousCluster > fromAngle && previousCluster.AngleToPreviousCluster < toAngle)) {
					return false;
				}
				count++;
				if (count > HistoryDeep) {
					break;
				}
			}
			return count > 0 ? true : false;
		}

		private bool IsMovingLeft(IEnumerable<Cluster> m) {
			// http://en.wikipedia.org/wiki/Cosine#Cosine
			const double fromAngle = 3 * Math.PI / 4;
			const double toAngle = 5 * Math.PI / 4;
			int count = 0;
			foreach (Cluster previousCluster in m) {
				if (previousCluster.PreviousCluster == null) {
					continue;
				}
				if (!(previousCluster.AngleToPreviousCluster > fromAngle && previousCluster.AngleToPreviousCluster < toAngle)) {
					return false;
				}
				count++;
				if (count > HistoryDeep) {
					break;
				}
			}
			return count > 0 ? true : false;
		}

		private bool IsMovingBottom(IEnumerable<Cluster> m) {
			// http://en.wikipedia.org/wiki/Cosine#Cosine
			const double fromAngle = 5 * Math.PI / 4;
			const double toAngle = 7 * Math.PI / 4;
			int count = 0;
			foreach (Cluster previousCluster in m) {
				if (previousCluster.PreviousCluster == null) {
					continue;
				}
				if (!(previousCluster.AngleToPreviousCluster > fromAngle && previousCluster.AngleToPreviousCluster < toAngle)) {
					return false;
				}
				count++;
				if (count > HistoryDeep) {
					break;
				}
			}
			return count > 0 ? true : false;
		}
		#endregion

        private double GetPosChangeOfCluster(Cluster m) {
            List<Cluster> clusterHist = new List<Cluster>();

            Cluster prevCluster = m;
            for (int i = 0; i < HistoryDeep; i++) {
                if (prevCluster.PreviousCluster == null) {
                    break;
                }
                clusterHist.Add(prevCluster);
                prevCluster = prevCluster.PreviousCluster;
            }

            var d1 = getDistance(m, clusterHist[clusterHist.Count - 1]);
            if (clusterHist.Count > 2) {
                var d2 = getDistance(m, clusterHist[clusterHist.Count - 2]);
                if (d2 < d1)
                    return d2;
            }
            return d1;
        }

	    public double getDistance(Cluster c1, Cluster c2) {
            return Math.Pow(
                Math.Pow(c1.AverageXValue - c2.AverageXValue, 2) +
                Math.Pow(c1.AverageYValue - c2.AverageYValue, 2), 0.5);
        }

		private double GetCumulatedMovementOfCluster(Cluster m) {
			double historyCumulatedMovement = 0;

			Cluster prevCluster = m;
			for (int i = 0; i < HistoryDeep; i++) {
				if (prevCluster.PreviousCluster == null) {
					break;
				}
				historyCumulatedMovement += prevCluster.DistanceToPreviousCluster;
				prevCluster = prevCluster.PreviousCluster;
			}

			return historyCumulatedMovement;
		}

		private int GetClusterDeepness(Cluster c) {
			int ret = 0;
			Cluster prevCluster = c;
			for (int i = 0; i < HistoryDeep; i++) {
				if (prevCluster.PreviousCluster == null) {
					break;
				}
				ret++;
				prevCluster = prevCluster.PreviousCluster;
			}

			return ret;
		}

		protected override IList<Cluster> OnProcess(IList<Cluster> toProcess) {
			if (toProcess == null) {
				return toProcess;
			}
			//if (toProcess.Count == 0) {
			//    return toProcess;
			//}
			StatisticValuesProviderService.SetStatisticValue("Movement@ClusterCountTotal", toProcess.Count);

			CalcCardinalPointsMovement(toProcess);

			CalcClusterHistoryData(toProcess);

			//calc distance movement
			List<Cluster> goodClusters = CalculateActivity(true, toProcess);

			StatisticValuesProviderService.SetStatisticValue("Movement@ClusterCountGood", goodClusters.Count);

			return goodClusters;
		}

		private void CalculateAcivity(ICollection<Cluster> toProcess) {
			var imgSize = CurrentImageInformationLocatorService.GetImageSize();
			var imgNorm = Math.Pow(imgSize.Width * imgSize.Width + imgSize.Height * imgSize.Height, .5);

			double avgClusterActivity = 0;
			double cumulatedClusterAcvitity = 0;
			int amountOfNonZeroMovementClusters = 0;
			foreach (Cluster c in toProcess) {
				double historyCumulatedMovement = GetCumulatedMovementOfCluster(c);
				double poschange = GetPosChangeOfCluster(c);

				var maxdistance = (HistoryDeep * imgNorm);
				var clusterQuality = (historyCumulatedMovement - poschange) / (historyCumulatedMovement + poschange);
				var clusterMovement = historyCumulatedMovement * clusterQuality;
				var clusterActivity = clusterMovement / maxdistance;

				//var clusterActivity = (historyCumulatedMovement - poschange) / (HistoryDeep * imgNorm);
				//clusterActivity = Math.Max(0, clusterActivity); //Prevent - values because of rounding in walking points
				//for(int i=0;i<4;i++)
				//    clusterActivity = Math.Sqrt(clusterActivity);   //Adapt activity. Maximum activity is never reached. So we apply square rooting to have appropriate values.

				cumulatedClusterAcvitity += clusterActivity;
				if (clusterActivity != 0)
					amountOfNonZeroMovementClusters++;
			}
			if (amountOfNonZeroMovementClusters != 0)
				avgClusterActivity = cumulatedClusterAcvitity / amountOfNonZeroMovementClusters;

			var minval = 0.001d;
			var maxval = 0.005d;
			var outputvalue = 0d;
			if (avgClusterActivity <= minval)
				outputvalue = 0;
			else if (avgClusterActivity >= maxval)
				outputvalue = 1;
			else
				outputvalue = (avgClusterActivity - minval) / (maxval - minval);

			m_activityHistoryValue += outputvalue;
			StatisticValuesProviderService.SetStatisticValue("Movement@AverageClusterActivity", outputvalue);

			double totalMovementOfAllClusters = 0;
			foreach (Cluster c in toProcess) {
				totalMovementOfAllClusters += c.DistanceToPreviousCluster;
			}
			StatisticValuesProviderService.SetStatisticValue("Movement@TotalMovementOfAllClusters", totalMovementOfAllClusters);


			double averageMovementOfAllClusters = 0;
			foreach (Cluster c in toProcess) {
				averageMovementOfAllClusters += c.DistanceToPreviousCluster;
			}
			if (toProcess.Count != 0)
				averageMovementOfAllClusters /= toProcess.Count;
			StatisticValuesProviderService.SetStatisticValue("Movement@AverageMovementOfAllClusters", averageMovementOfAllClusters);

			double averageMovementOfAllClustersInClustersSize = 0;
			foreach (Cluster c in toProcess) {
				averageMovementOfAllClustersInClustersSize += c.DistanceToPreviousCluster / c.HulPerimeterSize;
			}
			if (toProcess.Count != 0)
				averageMovementOfAllClustersInClustersSize /= toProcess.Count;
			StatisticValuesProviderService.SetStatisticValue("Movement@AverageMovementInClusterSize", averageMovementOfAllClustersInClustersSize);
			//double totalMovementOfAllClusters = 0;
			//double averageMovementOfAllClustersInClustersSize = 0;
			//double averageActivity = 0;
			//int histclusters = 0;
			//foreach (Cluster c in toProcess) {
			//    totalMovementOfAllClusters += c.DistanceToPreviousCluster;
			//    averageMovementOfAllClustersInClustersSize += c.DistanceToPreviousCluster / c.HulPerimeterSize;

			//    double historyCumulatedMovement = GetCumulatedMovementOfCluster(c);
			//    double poschange = GetPosChangeOfCluster(c);

			//    double thisClusterAcitivity = historyCumulatedMovement / (poschange + 1);
			//    averageActivity += thisClusterAcitivity;
			//    histclusters++;
			//}
			//double averageMovementOfAllClusters = totalMovementOfAllClusters / toProcess.Count;
			//averageMovementOfAllClustersInClustersSize /= toProcess.Count;
			//if (histclusters != 0) {
			//    averageActivity /= histclusters;
			//}

			//m_activityHistoryValue += averageActivity;

			//StatisticValuesProviderService.SetStatisticValue("Movement@TotalMovementOfAllClusters", totalMovementOfAllClusters);
			//StatisticValuesProviderService.SetStatisticValue("Movement@AverageMovementOfAllClusters",
			//                                                  averageMovementOfAllClusters);
			//StatisticValuesProviderService.SetStatisticValue("Movement@AverageMovementInClusterSize",
			//                                                  averageMovementOfAllClustersInClustersSize);
			//StatisticValuesProviderService.SetStatisticValue("Movement@AverageClusterActivity", averageActivity);
		}

		private void CalculateCoverageRation(IEnumerable<Cluster> toProcess) {
			double totalSizeOfAllClusters = 0;
			foreach (Cluster m in toProcess) {
				totalSizeOfAllClusters += m.Points.Count;
			}

			var realSize = StatisticValuesLocatorService["ImageSizeRealRegions"] == null ? (int?)null : (int?)StatisticValuesLocatorService["ImageSizeRealRegions"].Value;
			if (!realSize.HasValue) {
				Size imageSize = CurrentImageInformationLocatorService.GetImageSize();
				realSize = imageSize.Width * imageSize.Height;
			}

			double clusterCoverageAreaRatio = totalSizeOfAllClusters / realSize.Value;

			m_coverageHistoryValue += clusterCoverageAreaRatio;
		}

		private List<Cluster> FilterClusters(IEnumerable<Cluster> toProcess) {
			var goodClusters = new List<Cluster>();

			foreach (Cluster m in toProcess) {
				double poschange = GetPosChangeOfCluster(m);
				int thisClusterDeep = GetClusterDeepness(m);
				if (poschange < 1 * m.HulPerimeterSize * (thisClusterDeep) * ScaleFactor) {
					goodClusters.Add(m);
				}
			}

			return goodClusters;
		}

		private List<Cluster> CalculateActivity(bool doFilteringFirst, ICollection<Cluster> toProcess) {
			List<Cluster> goodClusters = FilterClusters(toProcess);

			ICollection<Cluster> clusterOperateSet = doFilteringFirst ? goodClusters : toProcess;
			CalculateCoverageRation(clusterOperateSet);
			CalculateAcivity(clusterOperateSet);

			m_activityHistoryCounter++;

			if (m_activityHistoryCounter == HistoryDeepForRatingAveraging) {
				m_ActivityHistoryQueue.enqueue(m_activityHistoryValue / m_activityHistoryCounter);
				m_CoverageRatioHistoryQueue.enqueue(m_coverageHistoryValue / m_activityHistoryCounter);

				m_activityHistoryCounter = 0;
				m_activityHistoryValue = 0;
				m_coverageHistoryValue = 0;

				//StatisticValuesProviderService.SetValueToShow("Movement@ActivityRating", ActivityHistoryValue / ActivityHistoryCounter);
				StatisticValuesProviderService.SetStatisticValue("Movement@ActivityRating", m_ActivityHistoryQueue.getAvgVal());
				StatisticValuesProviderService.SetStatisticValue("Movement@ClusterAreaCoverageRatio",
																  m_CoverageRatioHistoryQueue.getAvgVal());

				if (m_ActivityHistoryQueue.getAvgVal() < m_activityHistoricalMinValue) {
					m_activityHistoricalMinValue = m_ActivityHistoryQueue.getAvgVal();
				}
				if (m_ActivityHistoryQueue.getAvgVal() > m_activityHistoricalMaxValue) {
					m_activityHistoricalMaxValue = m_ActivityHistoryQueue.getAvgVal();
				}
				StatisticValuesProviderService.SetStatisticValue("Movement@HistoricalMinClusterAcivity",
																  m_activityHistoricalMinValue);
				StatisticValuesProviderService.SetStatisticValue("Movement@HistoricalMaxClusterAcivity",
																  m_activityHistoricalMaxValue);
			}

			return goodClusters;
		}

		private void CalcCardinalPointsMovement(IEnumerable<Cluster> toProcess) {
			int movementToRight = 0;
			int movementToLeft = 0;
			int movementToTop = 0;
			int movementToBottom = 0;
			int movementOther = 0;
			foreach (Cluster m in toProcess) {
				if (IsMovingRight(m)) {
					movementToRight++;
				}
				else if (IsMovingLeft(m)) {
					movementToLeft++;
				}
				else if (IsMovingTop(m)) {
					movementToTop++;
				}
				else if (IsMovingBottom(m)) {
					movementToBottom++;
				}
				else {
					movementOther++;
				}
			}
			StatisticValuesProviderService.SetStatisticValue("Movement@Right", movementToRight);
			StatisticValuesProviderService.SetStatisticValue("Movement@Left", movementToLeft);
			StatisticValuesProviderService.SetStatisticValue("Movement@Bottom", movementToBottom);
			StatisticValuesProviderService.SetStatisticValue("Movement@Top", movementToTop);
			StatisticValuesProviderService.SetStatisticValue("Movement@Other", movementOther);
		}

		private void CalcClusterHistoryData(ICollection<Cluster> toProcess) {
			int minDeep = int.MaxValue;
			int maxDeep = 0;
			int avgDeep = 0;
			foreach (Cluster m in toProcess) {
				int deep = 0;
				Cluster prevCluster = m;
				do {
					prevCluster = prevCluster.PreviousCluster;
					deep++;
				} while (prevCluster != null);
				if (minDeep > deep) {
					minDeep = deep;
				}
				if (maxDeep < deep) {
					maxDeep = deep;
				}
				avgDeep += deep;
			}
			if (toProcess.Count != 0) {
				avgDeep /= toProcess.Count;
			}
			StatisticValuesProviderService.SetStatisticValue("Movement@MinClusterDeep", minDeep);
			StatisticValuesProviderService.SetStatisticValue("Movement@MaxClusterDeep", maxDeep);
			StatisticValuesProviderService.SetStatisticValue("Movement@AvgClusterDeep", avgDeep);
		}

		#endregion

		#region Private Fields

		private readonly LimitedQueue m_ActivityHistoryQueue;
		private readonly LimitedQueue m_CoverageRatioHistoryQueue;
		private double m_activityHistoricalMaxValue;
		private double m_activityHistoricalMinValue = double.MaxValue;
		private int m_activityHistoryCounter;
		private double m_activityHistoryValue;
		private double m_coverageHistoryValue;

		#endregion

		#region Nested type: LimitedQueue

		public class LimitedQueue {
			private readonly Queue<double> m_data;
			private readonly int size;

			public LimitedQueue(int size) {
				this.size = size;
				m_data = new Queue<double>(size);
			}

			public void Clear() {
				m_data.Clear();
			}

			public void enqueue(double val) {
				if (double.IsNaN(val))
					val = 0;
				m_data.Enqueue(val);
				if (m_data.Count > size) {
					m_data.Dequeue();
				}
			}

			public double getAvgVal() {
				double res = 0;
				double[] dataarray = m_data.ToArray();
				double weight = 0d;
				int len = Math.Min(m_data.Count, size);
				for (int i = 0; i < len; i++) {
					//var factor = (1/((double)i+1));       // 1/x history rating
					double factor = 1 - i * (1 / (double)len); // linear history rating
					weight += factor;
					res += factor * dataarray[m_data.Count - i - 1];
				}
				return res / weight;
			}
		}

		#endregion
	}
}